'use strict';

const jwt = require('jsonwebtoken');
const Service = require('egg').Service;

class UserService extends Service {
  constructor(ctx) {
    super(ctx);
    this.user = {};
  }

  async login(params) {
    const { config, mysql, redis, Sequelize: { Op }, model } = this.app;
    const { Sys: { User, UserInfo, Resource, Permission, Role } } = model;
    const policies = config.policies;

    try {
      const { username, password } = params;
      const user = await User.findOne({
        attributes: { exclude: [ 'password' ] },
        include: { model: UserInfo, as: 'info', attributes: { exclude: [ 'user_id' ] } },
        where: {
          user_name: username,
          password,
        },
      });
      if (user) {
        const { id, info } = user;
        // roles
        const roles = await user.getRoles();
        // permission
        const resources = await Resource.findAll({
          include: [{
            model: Permission,
            as: 'permissions',
            include: [{
              model: Role,
              as: 'roles',
              through: {
                attributes: [ 'permission_id', 'role_id' ],
                where: {
                  role_id: {
                    [Op.in]: roles.map(item => item.id),
                  },
                },
              },
            }],
            through: {
              attributes: [ ],
            },
          }],
        });
        // console.log(resources);
        const sqlstr =
          'SELECT `sys_permission_resource`.resource_id,`sys_permission_resource`.permission_id,`sys_resource`.`name`,`sys_resource`.url,`sys_permission`.action FROM `sys_permission`,`sys_resource`,`sys_permission_resource` WHERE `sys_permission`.id=`sys_permission_resource`.permission_id AND `sys_resource`.id=`sys_permission_resource`.resource_id AND `sys_permission`.id IN (SELECT `sys_role_permission`.permission_id FROM `sys_role`,`sys_permission`,`sys_role_permission` WHERE `sys_role`.id=`sys_role_permission`.role_id AND `sys_permission`.id=`sys_role_permission`.permission_id AND `sys_role`.id IN (SELECT `sys_role`.id FROM `sys_user`,`sys_role`,`sys_user_role` WHERE `sys_user`.id=`sys_user_role`.user_id AND `sys_role`.id=`sys_user_role`.role_id AND `sys_user`.id=?))';
        const permission = await mysql.query(sqlstr, [ id ]);
        // redis
        const authToken = jwt.sign({ id, username }, policies.jwt.secret, {
          expiresIn: policies.jwt.expiresIn,
        });
        await redis.set(
          id,
          JSON.stringify(
            Object.assign({ authToken }, { id, username, permission })
          )
        );
        // result
        return {
          authToken,
          id,
          username,
          info,
          roles,
          resources,
        };
      }
      this.ctx.throw(500, '用户名或密码不正确');
    } catch (error) {
      this.ctx.throw(500, error.message);
    }
  }

  async logout({ id }) {
    const { redis } = this.app;
    try {
      await redis.del(id);
    } catch (error) {
      this.ctx.throw(500, error.message);
    }
  }

  async create(params) {
    const { mysql } = this.app;
    const conn = await mysql.beginTransaction();
    const { username, password } = params;
    const created_at = new Date();
    try {
      const result = await conn.query("select REPLACE(UUID(),'-','') as uuid");
      const id = result[0].uuid;
      await conn.query(
        'INSERT INTO sys_user(id, user_name, password, created_at, updated_at) VALUES (?, ?, ?, ?, ?)',
        [ id, username, password, created_at, null ]
      );
      await conn.insert('sys_user_info', {
        user_id: id,
        created_at,
      });
      await conn.commit();
      return { id };
    } catch (err) {
      await conn.rollback();
      throw err;
    }
  }

  async find(params) {
    const { mysql } = this.app;
    const {
      helper: { pagination },
    } = this.ctx;

    try {
      const { nickName, page, size } = params;
      const pagger = pagination(page, size);
      const filters = {
        nick_name: { '=': nickName },
      };
      const segments = [];
      const values = [];
      Object.keys(filters).forEach(key => {
        const item = filters[key];
        const k = Object.keys(item)[0];
        if (item[k]) {
          segments.push(`(${key} ${k} ? )`);
          values.push(item[k]);
        }
      });

      const select =
        'SELECT U.`id`, U.`user_name`, U.`enabled`, U.`created_at`, U.`updated_at`, I.`avatar`, I.`nick_name`, I.`email`, I.`age` FROM `sys_user` AS U LEFT JOIN ( SELECT * FROM `sys_user_info` ) AS I ON (U.`id` = I.`user_id`)';
      const wheres = segments.length ? `WHERE ${segments.join(' OR ')}` : '';
      const order = 'ORDER BY `updated_at` DESC';
      const limit = 'LIMIT ? OFFSET ?';

      const count = await mysql.query(
        'SELECT COUNT(*) as `total` FROM (' + select + ') C ' + wheres,
        [ ...values ]
      );
      const result = await mysql.query(
        [ select, wheres, order, limit ].join(' '),
        [ ...values, pagger.limit, pagger.offset ]
      );

      return {
        pagination: {
          total: count[0].total,
          page: pagger.page,
          size: pagger.size,
        },
        list: result,
      };
    } catch (error) {
      this.ctx.throw(500, error.message);
    }
  }

  async findOne(params) {
    const { mysql } = this.app;
    try {
      const { id } = params;
      const select =
        'SELECT U.`id`, U.`user_name`, U.`enabled`, U.`created_at`, U.`updated_at`, I.`avatar`, I.`nick_name`, I.`email`, I.`age`, I.`avatar` FROM `sys_user` AS U LEFT JOIN ( SELECT * FROM `sys_user_info` ) AS I ON (U.`id` = I.`user_id`)';
      const wheres = 'WHERE U.`id` = ?';
      const result = await mysql.query([ select, wheres ].join(' '), [ id ]);
      if (result.length) {
        return result[0];
      }
      this.ctx.throw(404, 'the id was not found');
    } catch (error) {
      this.ctx.throw(error.status || 500, error.message);
    }
  }

  async update(params) {
    const { mysql } = this.app;
    const conn = await mysql.beginTransaction();
    const { id, email, nickName, age, avatar, roleId } = params;

    try {
      await conn.update(
        'sys_user_info',
        {
          email,
          nick_name: nickName,
          age,
          avatar,
        },
        {
          where: {
            user_id: id,
          },
        }
      );
      if (roleId) {
        await conn.insert('sys_user_role', { user_id: id, role_id: roleId });
      }
      await conn.commit();
      return { id };
    } catch (err) {
      await conn.rollback();
      throw err;
    }
  }

  async destroy() {
    return {};
  }
}

module.exports = UserService;
